//@author A0081058H
package net.mysoc.w111j.ui;

import java.io.IOException;

import javafx.stage.Stage;
import net.mysoc.w111j.service.CommandService;
import net.mysoc.w111j.service.ExpenseService;
import net.mysoc.w111j.ui.expensedetail.ExpenseDetailModel;
import net.mysoc.w111j.ui.expensedetail.ExpenseDetailPresenter;
import net.mysoc.w111j.ui.expensedetail.ExpenseDetailView;
import net.mysoc.w111j.ui.expenselist.ExpenseListModel;
import net.mysoc.w111j.ui.expenselist.ExpenseListPresenter;
import net.mysoc.w111j.ui.expenselist.ExpenseListView;
import net.mysoc.w111j.ui.generatereport.GenerateReportModel;
import net.mysoc.w111j.ui.generatereport.GenerateReportPresenter;
import net.mysoc.w111j.ui.generatereport.GenerateReportView;
import net.mysoc.w111j.ui.main.MainModel;
import net.mysoc.w111j.ui.main.MainPresenter;
import net.mysoc.w111j.ui.main.MainView;

/**
 * Factory class for presenters and services.
 */
public class AppFactory {
    private MainPresenter mainPresenter;
    private ExpenseDetailPresenter expenseDetailPresenter;
    private ExpenseListPresenter expenseListPresenter;
    private GenerateReportPresenter generateReportPresenter;
    private ExpenseService expenseService;
    private CommandService commandService;
    private final Stage stage;

    /**
     * Constructor. Takes in the main {@code Stage} used by the application. {@code MainPresenter} requires a
     * {@code Stage} to spawn dialog windows which are children of the application's primary {@code Stage}.
     *
     * @param stage the {@code Stage} used by the application.
     */
    AppFactory(Stage stage) {
        this.stage = stage;
    }

    /**
     * Creates and returns a {@code MainPresenter} linked to a new {@code MainModel} and a new {@code MainView}
     * if there is no existing {@code MainPresenter}. If there is an existing {@code MainPresenter}, it simply
     * returns that.
     *
     * @return the application {@code MainPresenter}.
     */
    public MainPresenter getMainPresenter() {
        if (this.mainPresenter == null) {
            MainModel model = new MainModel();
            MainView view = new MainView(model);
            this.mainPresenter = new MainPresenter(model, view, this.stage, getExpenseService());
            this.mainPresenter.setCommandService(getCommandService());
            this.mainPresenter.setExpenseDetailPresenter(getExpenseDetailPresenter());
            this.mainPresenter.setExpenseListPresenter(getExpenseListPresenter());
            this.mainPresenter.setGenerateReportPresenter(getGenerateReportPresenter());
            this.mainPresenter.setDefaultView();
        }

        return this.mainPresenter;
    }

    /**
     * Creates and returns an {@code ExpenseDetailPresenter} linked to a new {@code ExpenseDetailModel} and a new
     * {@code ExpenseDetailView} if there is no existing {@code ExpenseDetailPresenter}. If there is an existing
     * {@code ExpenseDetailPresenter}, it simply returns that.
     *
     * @return the application {@code ExpenseDetailPresenter}.
     */
    public ExpenseDetailPresenter getExpenseDetailPresenter() {
        if (this.expenseDetailPresenter == null) {
            ExpenseDetailModel model = new ExpenseDetailModel();
            ExpenseDetailView view = new ExpenseDetailView(model);
            this.expenseDetailPresenter = new ExpenseDetailPresenter(model, view);
            this.expenseDetailPresenter.setExpenseService(getExpenseService());
        }

        return this.expenseDetailPresenter;
    }

    /**
     * Creates and returns an {@code ExpenseListPresenter} linked to a new {@code ExpenseListModel} and a new
     * {@code ExpenseListView} if there is no existing {@code ExpenseListPresenter}. If there is an existing
     * {@code ExpenseListPresenter}, it simply returns that.
     *
     * @return the application {@code ExpenseListPresenter}.
     */
    public ExpenseListPresenter getExpenseListPresenter() {
        if (this.expenseListPresenter == null) {
            ExpenseListModel model = new ExpenseListModel();
            ExpenseListView view = new ExpenseListView(model);
            view.setExpenseService(getExpenseService());
            this.expenseListPresenter = new ExpenseListPresenter(model, view, getExpenseService(), getMainPresenter());
        }

        return this.expenseListPresenter;
    }

    /**
     * Creates and returns an {@code GenerateReportPresenter} linked to a new {@code GenerateReportModel} and a new
     * {@code GenerateReportView} if there is no existing {@code GenerateReportPresenter}. If there is an existing
     * {@code GenerateReportPresenter}, it simply returns that.
     *
     * @return the application {@code GenerateReportPresenter}.
     */
    public GenerateReportPresenter getGenerateReportPresenter() {
        if (this.generateReportPresenter == null) {
            GenerateReportModel model = new GenerateReportModel();
            GenerateReportView view = new GenerateReportView(model);
            this.generateReportPresenter = new GenerateReportPresenter(model, view, getExpenseService());
        }

        return this.generateReportPresenter;
    }

    /**
     * Creates and returns an {@code ExpenseService} if there is no existing {@code ExpenseService}.
     * If there is an existing {@code GenerateReportPresenter}, it simply returns that.
     *
     * @return the application {@code ExpenseService}.
     */
    public ExpenseService getExpenseService() {
        if (this.expenseService == null) {
            try {
                this.expenseService = new ExpenseService();
            } catch (IOException e) {
                // TODO: Handle this error somewhere
            }
        }

        return this.expenseService;
    }

    public CommandService getCommandService() {
        if (this.commandService == null) {
            this.commandService = new CommandService();
            this.commandService.setMainPresenter(getMainPresenter());
            this.commandService.setExpenseListPresenter(getExpenseListPresenter());
            this.commandService.setExpenseService(getExpenseService());
        }

        return this.commandService;
    }
}
